package cn.dengta.webapp.base.form;

import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.util.Map;
import java.util.TreeMap;
import javax.annotation.Nullable;
import javax.servlet.http.HttpServletRequest;

import cn.dengta.common.context.Context;
import cn.dengta.common.security.CryptoSuite;
import cn.dengta.common.security.RSA2Cipher;
import cn.dengta.common.web.Client;
import cn.dengta.context.model.BaseI18nKey;
import cn.dengta.context.web.CryptoSuiteContext;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.Feature;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import me.codeplayer.util.Assert;
import me.codeplayer.util.StringUtil;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

/**
 * body属性为 JSON 格式的抽象表单接口标识
 */
@Getter
@Setter
@Slf4j
public abstract class JsonBodyForm<E> extends BaseForm<E> {

	protected String body;
	protected E jsonBody;

	private static CryptoSuiteContext cryptoSuiteContext;

	@SuppressWarnings("unchecked")
	public final void setBody(String body) throws UnsupportedEncodingException {
		if (StringUtil.notEmpty(body)) {
			HttpServletRequest request = loadDecryptRequest();
			if (request != null) { // 需要解密
				CryptoSuiteContext context = cryptoSuiteContext;
				if (context == null) {
					cryptoSuiteContext = context = Context.getBean(CryptoSuiteContext.class);
				}
				Client client = Client.getClient(request);
				CryptoSuite suite = context.getCryptoSuite(client);
				/*
				#### 请求数据加密、签名
				1. 发送请求数据时，**只需要**对 `body` 参数（该参数数据要么为空，要么一定是JSON格式）的参数值进行加密。
				2.
				*/
				String timestamp = request.getHeader("Dt-timestamp");
				String dt_sign = request.getHeader("Dt-Sign");
				// long time = Long.parseLong(timestamp);
				// Assert.isTrue(Math.abs(time - System.currentTimeMillis()) <= context.getAllowMaxDelay(), BaseI18nKey.SECURITY_CLIENT_TIME_INVALID);
				Assert.notEmpty(dt_sign, BaseI18nKey.SECURITY_SIGN_REQUIRED);
				final String sign = URLDecoder.decode(dt_sign, StandardCharsets.UTF_8);
				String source = suite.getAes().decrypt(body);
				Map<String, String[]> parameterMap = request.getParameterMap();
				String signSource;
				if (parameterMap.size() > 1) {
					int capacity = timestamp.length() + 1 + source.length();
					final TreeMap<String, String> map = new TreeMap<>();
					for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {
						String name = entry.getKey();
						if (StringUtil.notEmpty(name) && !"body".equals(name)) {
							String[] values = entry.getValue();
							if (values != null && values.length == 1 && StringUtil.notEmpty(values[0])) {
								map.put(name, values[0]);
								capacity += values[0].length() + 1;
							}
						}
					}
					final char sep = '\n';
					final StringBuilder sb = new StringBuilder(capacity)
							.append(timestamp).append(sep);
					for (String value : map.values()) {
						sb.append(value).append(sep);
					}
					signSource = sb.append(source).toString();
				} else {
					signSource = timestamp.concat(source);
				}
				boolean verifyResult;
				RSA2Cipher rsa = suite.getRsa();
				try {
					verifyResult = rsa.verifySign(signSource, sign);
				} catch (NoSuchAlgorithmException | InvalidKeyException | SignatureException e) {
					throw new IllegalArgumentException(BaseI18nKey.SECURITY_SIGN_ERROR, e);
				}
				Assert.isTrue(verifyResult, BaseI18nKey.SECURITY_SIGN_ERROR);
				body = source;
			}
			// TODO 后续还要优化为无需拼接，直接获取
			JsonBodyForm<E> form = JSON.parseObject("{\"jsonBody\":" + body + "}", getClass(), (Feature[]) null);
			jsonBody = form.getJsonBody();
		}
		this.body = body;
	}

	@Nullable
	protected HttpServletRequest loadDecryptRequest() {
		ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
		return doLoadDecryptRequest(attributes.getRequest());
	}

	@Nullable
	protected HttpServletRequest doLoadDecryptRequest(HttpServletRequest request) {
		return request;
	}

}
